# -*- coding: utf-8 -*-
"""
core/代码应用/applier.py

代码应用模块。
负责根据LLM生成的结构化变更指令，自动修改本地代码文件。
特别注意处理GBK编码的旧项目文件。
"""

import os
from core.utils import logger, read_file_content, write_file_content

class CodeApplier:
    """
    代码应用器类，封装了将LLM建议应用到代码文件的逻辑。
    """
    def __init__(self, target_code_base_dir):
        """
        初始化代码应用器。

        参数:
            target_code_base_dir (str): 待修改的目标代码库的根目录。
                                       (通常是 old_project_code 的一个副本)
        """
        self.target_code_base_dir = target_code_base_dir
        if not os.path.isdir(self.target_code_base_dir):
            logger.error(f"目标代码库目录不存在: {self.target_code_base_dir}")
            # 在实际流程中，主脚本会确保此目录存在 (通常是副本)
            # raise ValueError(f"目标代码库目录不存在: {self.target_code_base_dir}")

    def _apply_upsert_operation(self, operation):
        """
        处理 "upsert" 类型的操作：新增文件或完整替换文件内容。
        对于嵌入式C项目，特别是旧项目，文件编码通常是GBK。

        参数:
            operation (dict): 包含操作详情的字典，例如：
                {"type": "upsert", "file_type": ".h", "file_name": "key.h", 
                 "code_content": "...", "description": "..."}
        返回:
            bool: 操作成功返回True，否则返回False。
        """
        file_name = operation.get("file_name")
        code_content = operation.get("code_content")
        file_type = operation.get("file_type") # .c or .h

        if not file_name or code_content is None:
            logger.error(f"Upsert操作缺少文件名或代码内容: {operation}")
            return False

        # 确定文件的绝对路径
        # 假设 file_name 可能包含相对路径，或者就是纯文件名
        # 如果LLM给出的文件名是相对于项目根目录的，例如 "src/key.h"，需要正确拼接
        # 为简单起见，如果文件名不含路径分隔符，则直接在根目录下创建/修改
        # 更好的做法是LLM明确给出相对于项目根目录的路径
        if os.path.sep in file_name or ("/" in file_name and os.path.sep == "/"):
            # 假设 file_name 是类似 "key.h" 或 "some_module/key.h"
            # 如果是 "some_module/key.h"，则需要确保 some_module 目录存在
            # 如果是绝对路径，则需要判断是否在 target_code_base_dir 内，出于安全考虑，通常不允许写到外面
            # 当前简化：假设 file_name 是相对于 target_code_base_dir 的路径
            full_file_path = os.path.join(self.target_code_base_dir, file_name)
        else:
            # 如果只是文件名，则放在根目录下 (这可能不符合C项目结构)
            # 更好的做法是，LLM的输出应包含更完整的路径信息，或者主控流程根据文件名和类型决定存放位置
            # 例如，所有.h文件放include/，所有.c文件放src/
            # 鉴于TM56F1522项目结构是扁平的，直接放根目录是符合的
            full_file_path = os.path.join(self.target_code_base_dir, file_name)
        
        # 确定文件编码：对于.c和.h文件，默认为GBK以匹配旧项目
        # 其他类型文件（如配置文件）可能是UTF-8
        encoding = "gbk" if file_type in [".c", ".h"] else "utf-8"

        logger.info(f"准备对文件 	'{full_file_path}	' 执行UPSERT操作 (编码: {encoding})。")
        
        # 创建目标文件的父目录 (如果不存在)
        try:
            os.makedirs(os.path.dirname(full_file_path), exist_ok=True)
        except Exception as e:
            logger.error(f"创建目录 {os.path.dirname(full_file_path)} 失败: {e}")
            return False

        if write_file_content(full_file_path, code_content, encoding=encoding):
            logger.info(f"文件 	'{full_file_path}	' UPSERT成功。")
            return True
        else:
            logger.error(f"文件 	'{full_file_path}	' UPSERT失败。")
            return False

    def _apply_replace_specific_operation(self, operation):
        """
        处理 "replace_specific" 类型的操作：替换文件中的特定代码段。
        【注意】此功能依赖于LLM输出精确的定位信息和待替换/替换后代码。
        当前 generator.py 中的解析器未完全实现此细粒度输出，此处为框架。

        参数:
            operation (dict): 包含操作详情的字典，例如：
                {"type": "replace_specific", "file_name": "led.c", 
                 "location_info": "函数 LED_Set_State 内，约第 55 行", 
                 "old_code_snippet": "...", "new_code_snippet": "..."}
        返回:
            bool: 操作成功返回True，否则返回False。
        """
        file_name = operation.get("file_name")
        old_code = operation.get("old_code_snippet")
        new_code = operation.get("new_code_snippet")
        location_info = operation.get("location_info", "") # 用于日志
        file_type = operation.get("file_type")

        if not file_name or old_code is None or new_code is None:
            logger.error(f"Replace_specific操作缺少必要信息: {operation}")
            return False

        full_file_path = os.path.join(self.target_code_base_dir, file_name)
        encoding = "gbk" if file_type in [".c", ".h"] else "utf-8"

        logger.info(f"准备对文件 	'{full_file_path}	' 执行REPLACE_SPECIFIC操作 (编码: {encoding})。定位: {location_info}")

        original_content = read_file_content(full_file_path, default_encoding=encoding)
        if original_content is None:
            logger.error(f"无法读取原始文件内容: {full_file_path}。无法执行替换。")
            return False

        # 执行替换
        # 注意：简单的字符串替换可能不安全，如果旧代码片段在文件中出现多次。
        # 更安全的方法是结合行号或更精确的上下文锚点，但这需要LLM提供更复杂的定位信息。
        # 假设 old_code 是唯一的，或者我们只替换第一个匹配项。
        if old_code not in original_content:
            logger.warning(f"在文件 	'{full_file_path}	' 中未找到待替换的旧代码片段。定位信息: {location_info}")
            logger.debug(f"旧代码片段:\n{old_code}")
            # 可以尝试基于 location_info 进行更模糊的定位，或标记为人工处理
            return False # 或者 True 如果认为“未找到即无需修改”算成功

        modified_content = original_content.replace(old_code, new_code, 1) # 只替换第一个匹配
        
        if modified_content == original_content:
             logger.warning(f"代码替换操作未改变文件内容: {full_file_path}。可能旧代码与新代码相同，或替换逻辑问题。")
             # 这种情况通常也算成功，因为文件已经是期望状态或无法修改
             return True

        if write_file_content(full_file_path, modified_content, encoding=encoding):
            logger.info(f"文件 	'{full_file_path}	' REPLACE_SPECIFIC成功。")
            return True
        else:
            logger.error(f"文件 	'{full_file_path}	' REPLACE_SPECIFIC写入失败。")
            return False
    
    def _apply_general_suggestion(self, operation):
        """
        处理 "general_suggestion" 类型的操作。
        通常这意味着LLM的输出未能被解析为具体的文件操作，需要人工介入。
        """
        description = operation.get("description", "")
        code_content = operation.get("code_content", "") # 整个LLM响应
        logger.warning(f"收到通用建议 (需要人工处理): {description}")
        logger.info(f"LLM原始建议内容:\n{code_content[:500]}...")
        # 此处不执行任何文件修改，返回True表示已处理（记录）此建议
        return True

    def apply_changes(self, operations):
        """
        应用LLM生成的一系列代码变更指令。

        参数:
            operations (list): 包含结构化变更指令的列表。

        返回:
            tuple: (succeeded_ops_count, failed_ops_count)
        """
        if not os.path.isdir(self.target_code_base_dir):
            logger.error(f"目标代码库目录在执行apply_changes时仍不存在: {self.target_code_base_dir}")
            return 0, len(operations)
            
        succeeded_ops = 0
        failed_ops = 0

        if not operations:
            logger.info("没有需要应用的代码变更操作。")
            return 0, 0

        logger.info(f"开始应用 {len(operations)} 条代码变更操作到目录: {self.target_code_base_dir}")

        for op in operations:
            op_type = op.get("type")
            success = False
            if op_type == "upsert":
                success = self._apply_upsert_operation(op)
            elif op_type == "replace_specific":
                # 当前 generator.py 的解析器主要产生 upsert，此分支作为扩展
                logger.info("接收到 replace_specific 操作，尝试应用...")
                success = self._apply_replace_specific_operation(op)
            elif op_type == "general_suggestion":
                success = self._apply_general_suggestion(op)
            else:
                logger.error(f"未知的操作类型: {op_type}。操作内容: {op}")
                success = False
            
            if success:
                succeeded_ops += 1
            else:
                failed_ops += 1
        
        logger.info(f"代码变更应用完成。成功: {succeeded_ops} 条, 失败: {failed_ops} 条。")
        return succeeded_ops, failed_ops

if __name__ == "__main__":
    project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
    mock_target_dir = os.path.join(project_root, "data", "output", "mock_code_apply_test")
    
    # 清理并创建测试目录
    if os.path.exists(mock_target_dir):
        import shutil
        shutil.rmtree(mock_target_dir)
    os.makedirs(mock_target_dir)

    # 创建一个模拟的旧文件 (GBK编码)
    mock_old_file_name = "test_module.c"
    mock_old_file_path = os.path.join(mock_target_dir, mock_old_file_name)
    initial_gbk_content = "// 这是旧的C文件内容\nvoid old_function() { printf(\"你好 GBK世界!\"); }"
    write_file_content(mock_old_file_path, initial_gbk_content, encoding="gbk")
    logger.info(f"创建了模拟的GBK文件: {mock_old_file_path}")

    applier = CodeApplier(target_code_base_dir=mock_target_dir)

    # 模拟的变更指令 (来自 generator.py 的输出)
    mock_operations = [
        {
            "type": "upsert",
            "file_type": ".h",
            "file_name": "new_module.h",
            "code_content": "// 这是新的 new_module.h 文件内容\n#define NEW_MACRO 123\nvoid new_header_func(void);",
            "description": "新增 new_module.h"
        },
        {
            "type": "upsert",
            "file_type": ".c",
            "file_name": "test_module.c", # 替换已存在的GBK文件
            "code_content": "// 这是被LLM修改后的 test_module.c 文件内容 (应为GBK)\n#include \"new_module.h\"\nvoid updated_function() { printf(\"你好，新世界!\"); }\nvoid another_new_func() { return; }",
            "description": "更新 test_module.c"
        },
        {
            "type": "replace_specific", # 假设 generator.py 能产生这种类型
            "file_type": ".c",
            "file_name": "another_existing.c", # 需要先创建这个文件
            "old_code_snippet": "int value = 10;",
            "new_code_snippet": "int value = 20; // updated by LLM",
            "location_info": "在 some_function 内部"
        },
        {
            "type": "general_suggestion",
            "code_content": "建议重构整个 main.c 文件以提高可读性。",
            "description": "LLM的通用重构建议"
        }
    ]
    
    # 为 replace_specific 创建一个目标文件
    another_c_file_path = os.path.join(mock_target_dir, "another_existing.c")
    write_file_content(another_c_file_path, "void some_function() {\n  int value = 10;\n  char* msg = \"原始消息\";\n}", encoding="gbk")

    logger.info("\n--- 测试代码应用模块 --- ")
    s_ops, f_ops = applier.apply_changes(mock_operations)
    logger.info(f"测试完成。成功操作: {s_ops}, 失败操作: {f_ops}")

    # 验证文件内容
    logger.info("\n--- 验证文件内容 ---")
    new_h_content = read_file_content(os.path.join(mock_target_dir, "new_module.h"), default_encoding="gbk")
    if new_h_content:
        logger.info(f"new_module.h 内容 (应为GBK):
{new_h_content}")
        assert "NEW_MACRO 123" in new_h_content

    updated_c_content = read_file_content(os.path.join(mock_target_dir, "test_module.c"), default_encoding="gbk")
    if updated_c_content:
        logger.info(f"test_module.c 更新后内容 (应为GBK):
{updated_c_content}")
        assert "你好，新世界!" in updated_c_content
        assert initial_gbk_content not in updated_c_content # 确认被覆盖
        
    replaced_c_content = read_file_content(another_c_file_path, default_encoding="gbk")
    if replaced_c_content:
        logger.info(f"another_existing.c 更新后内容 (应为GBK):
{replaced_c_content}")
        assert "int value = 20; // updated by LLM" in replaced_c_content
        assert "int value = 10;" not in replaced_c_content

    logger.info("\n代码应用器模块独立测试完成。")


